﻿package kit4j.view.cs.swing.component;

import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JList;

import kit4j.view.cs.exception.ListItemNotFoundException;
import kit4j.view.cs.exception.ListNoSelectionException;

/**
 * 显示字体列表并且允许用户选择一个或多个项的组件。使用方法类似JList。 以下代码创建一个显示当前系统环境所有字体的列表并选择字体名为"宋体"的项，然后打印列表当前选择的字体信息：
 * 
 * <pre>
 * <code>
 * 	JFontList fontList = new JFontList(GraphicsEnvironment
 * 				.getLocalGraphicsEnvironment().getAllFonts());
 * 	fontList.setSelectedFont(Font.decode("宋体"));
 * 	System.out.println(fontList.getSelectedFont());
 * </code>
 * </pre>
 * 
 * @author MiniKnife
 * 
 */
public class JFontList extends JList {

	private static final long serialVersionUID = 1L;

	/**
	 * 当前系统环境所支持的所有字体。
	 */
	public static final Font[] ALL_FONTS = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();

	/**
	 * 字体名称-字体对应集。字体列表通过它使用字体名称定位到列表中的某个字体。
	 */
	private Map<String, Font> fontNameMap;

	public JFontList() {
		this(null);
	}

	public JFontList(Font[] fonts) {
		super();

		setCellRenderer(new JFontListCellRenderer());

		fontNameMap = new HashMap<String, Font>();

		if (fonts != null) {
			setListData(fonts);
		} else {
			setListData(ALL_FONTS);
		}
	}

	/**
	 * 返回最小的选择单元索引的字体；只选择了列表中单个项时，返回所选值。选择了多项时，返回最小的选择索引的值。
	 * 
	 * @return 当所选的第一个字体
	 * @throws ListNoSelectionException
	 *             如果列表什么也没有选择，抛出此异常。
	 */
	public Font getSelectedFont() throws ListNoSelectionException {
		Font font = (Font) super.getSelectedValue();

		if (font == null) {
			throw new ListNoSelectionException();
		}

		return font;
	}

	/**
	 * 从字体列表中选择指定的字体。
	 * 
	 * @param font
	 *            要选择的字体
	 * @param shouldScroll
	 *            如果所选字体存在，但列表需要滚动才能显示，则为 true；否则为 false
	 * @throws ListItemNotFoundException
	 *             如果列表中不存在此字体，则抛出此异常。
	 */
	public void setSelectedFont(Font font, boolean shouldScroll) throws ListItemNotFoundException {
		if (font == null) {
			return;
		}

		Font f = fontNameMap.get(font.getName());
		if (f != null) {
			super.setSelectedValue(f, shouldScroll);
		}
	}

	/**
	 * 返回所有选择字体的数组，根据其列表中的索引顺序按升序排序。
	 * 
	 * @return 所选字体
	 * @throws ListNoSelectionException
	 *             如果列表什么也没有选择，抛出此异常。
	 */
	public Font[] getSelectedFonts() throws ListNoSelectionException {
		Font[] fonts = (Font[]) super.getSelectedValues();

		if (fonts.length == 0) {
			throw new ListNoSelectionException();
		}

		return fonts;
	}

	@Override
	/**
	 * 从字体列表中选择指定的字体。如果列表中不存在此字体，则什么也不做，建议使用替代方法
	 * JFontList#setSelectedFont(Font, boolean)。
	 * @param font 要选择的字体
	 * @param shouldScroll 如果所选字体存在，但列表需要滚动才能显示，则为 true；否则为 false
	 * @throws IllegalArgumentException 如果参数对象为null或参数不是Font类型，抛出此异常。
	 */
	public void setSelectedValue(Object font, boolean shouldScroll) throws IllegalArgumentException {
		if (font == null) {
			throw new IllegalArgumentException("Argument should't be null!");
		}

		if (!(font instanceof Font)) {
			throw new IllegalArgumentException("Argument must be a Font");
		}

		try {
			setSelectedFont((Font) font, shouldScroll);
		} catch (ListItemNotFoundException e) {
			return;
		}
	}

	@Override
	/**
	 * 根据一个字体对象数组构造只读 ListModel，然后对此模型调用 setModel。 
	 * @throws IllegalArgumentException 如果参数对象为null或参数不是Font类型数组，抛出此异常。
	 */
	public void setListData(Object[] fonts) throws IllegalArgumentException {
		if (fonts == null) {
			throw new IllegalArgumentException("Argument should't be null!");
		}

		if (!(fonts instanceof Font[])) {
			throw new IllegalArgumentException("Argument must be a Font list");
		}

		super.setListData(fonts);

		// 初始化名称-字体对应集
		fontNameMap.clear();
		for (Font f : (Font[]) fonts) {
			fontNameMap.put(f.getName(), f);
		}
	}

	/**
	 * 返回字体列表中的字体对象数组。
	 * 
	 * @return 字体对象数组
	 */
	public Font[] getListFonts() {
		return fontNameMap.values().toArray(new Font[0]);
	}

	/**
	 * 根据一个字体对象数组构造只读 ListModel，然后对此模型调用 setModel。
	 * 
	 * @throws IllegalArgumentException
	 *             如果参数对象为null或参数不是Font类型数组，抛出此异常。
	 */
	public void setListFonts(Font[] fonts) throws IllegalArgumentException {
		this.setListData(fonts);
	}

}
